package org.domus.sipnmove.useragent.impl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.ListeningPoint;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionAlreadyExistsException;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.AddressFactory;
import javax.sip.header.HeaderFactory;
import javax.sip.message.MessageFactory;

import org.domus.sipnmove.useragent.Call;
import org.domus.sipnmove.useragent.UserAgent;
import org.domus.sipnmove.useragent.UserAgentObserver;
import org.domus.sipnmove.useragent.UserInformation;

/**
 * Simple implementation of a user agent
 */
public class UserAgentImpl implements UserAgent, SipListener {

	private Set<UserAgentObserver> userAgentObservers;
	private AddressFactory addressFactory;
	private HeaderFactory headerFactory;
	private MessageFactory messageFactory;
	private UserInformation userInformation;
	private SipProvider sipProvider;
	private Map<String, Call> callsMap;

	/**
	 * Default constructor
	 */
	public UserAgentImpl(UserInformation userInformation) {
		userAgentObservers = new HashSet<UserAgentObserver>();
		callsMap = new HashMap<String, Call>();

		this.userInformation = userInformation;

		initSip();
	}

	/**
	 * Initialize SIP stuff
	 * 
	 * Code adapted from Jain-SIP examples
	 */
	private void initSip() {
		String ipAddress = userInformation.getIpAddress();
		Integer port = userInformation.getPort();

		SipFactory sipFactory = SipFactory.getInstance();
		sipFactory.setPathName("gov.nist");

		// The port number is used as a Unique ID
		Properties properties = new Properties();
		properties.setProperty("javax.sip.STACK_NAME", "useragent" + port);
		// You need 16 for logging traces. 32 for debug + traces.
		// Your code will limp at 32 but it is best for debugging.
		properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
		properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "ua" + port + "_debug.txt");
		properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "ua" + port + "_log.txt");

		try {
			// Create SipStack object
			SipStack sipStack = sipFactory.createSipStack(properties);

			headerFactory = sipFactory.createHeaderFactory();
			addressFactory = sipFactory.createAddressFactory();
			messageFactory = sipFactory.createMessageFactory();

			ListeningPoint listeningPoint = sipStack.createListeningPoint(ipAddress, port, "udp");

			sipProvider = sipStack.createSipProvider(listeningPoint);
			sipProvider.addSipListener(this);

		} catch (PeerUnavailableException e) {
			// could not find
			// gov.nist.jain.protocol.ip.sip.SipStackImpl
			// in the class path
			e.printStackTrace();
			System.err.println(e.getMessage());
			if (e.getCause() != null)
				e.getCause().printStackTrace();
			System.exit(0);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	private Call createCallObject() {
		CallImpl newCall = new CallImpl(this);
		newCall.setAddressFactory(addressFactory);
		newCall.setHeaderFactory(headerFactory);
		newCall.setMessageFactory(messageFactory);
		newCall.setSipProvider(sipProvider);

		return newCall;
	}

	@Override
	public void addUserAgentObserver(UserAgentObserver userAgentObserver) {
		userAgentObservers.add(userAgentObserver);
	}

	@Override
	public void createCall(String addressText) {
		Call newCall = createCallObject();

		newCall.initiate(addressText);

		// Keep track of the call by its Call ID for next SIP request/response
		callsMap.put(newCall.getCallId(), newCall);

		System.out.println(newCall.getCallId());

		notifyCallCreated(newCall);
	}

	@Override
	public void removeCall(String callId) {
		callsMap.remove(callId);
	}

	/**
	 * Notify all listeners that a call have been created
	 */
	private void notifyCallCreated(Call createdCall) {
		for (UserAgentObserver userAgentObserver : userAgentObservers) {
			userAgentObserver.callCreated(createdCall);
		}
	}

	@Override
	public void quit() {
		System.out.println("Quit the application ! Bye !");
		System.exit(0);
	}

	@Override
	public UserInformation getUserInformation() {
		return userInformation;
	}

	public void setUserInformation(UserInformation userInformation) {
		this.userInformation = userInformation;
	}

	@Override
	public void processDialogTerminated(DialogTerminatedEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void processIOException(IOExceptionEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void processRequest(RequestEvent requestEvent) {
		
		try {

			System.out.println(requestEvent.getRequest());
			ServerTransaction st = requestEvent.getServerTransaction();
			
			if (st == null) {

				st = sipProvider.getNewServerTransaction(requestEvent.getRequest());

			}

			String callId = st.getDialog().getCallId().getCallId();
			Call call;

			if (callsMap.containsKey(callId)) {
				call = callsMap.get(callId);
			} else {
				call = createCallObject();
			}

			callsMap.put(callId, call);

			call.processRequest(requestEvent, st);

		} catch (TransactionAlreadyExistsException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransactionUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void processResponse(ResponseEvent responseEvent) {

		String callId = responseEvent.getDialog().getCallId().getCallId();
		Call call = callsMap.get(callId);

		call.processResponse(responseEvent);
	}

	@Override
	public void processTimeout(TimeoutEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void processTransactionTerminated(TransactionTerminatedEvent arg0) {
		// TODO Auto-generated method stub

	}

}
